package org.crud4j.core;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.ConvertUtils;
import org.apache.commons.lang.ObjectUtils;

/**
 * Representation of the object subject to crud operations
 */
public class CrudBean {

	private static Comparator<CrudField> FIELD_POSITION_COMPARATOR = new Comparator<CrudField>() {
		public int compare(CrudField f1, CrudField f2) {
			if (f1.getDef().isId()) {
				return -1;
			} else if (f2.getDef().isId()) {
				return 1;
			} else {
				return f1.getDef().getPosition().compareTo(
						f2.getDef().getPosition());
			}
		}
	};

	/**
	 * The crud bean definition
	 */
	private CrudBeanDef def;

	/**
	 * The fields
	 */
	private Map<String, CrudField> fieldsMap;

	/**
	 * label for this crud bean instance
	 */
	private String label;

	/**
	 * Flags that indicates whether this crud bean is a new bean
	 */
	private boolean newBean;

	/**
	 * 
	 * @return the id of the bean
	 */
	public Object getId() {
		Collection<CrudField> crudIdFields = this.getIds();

		Object convertedId = null;
		if (crudIdFields.size() == 1) {
			CrudField crudField = crudIdFields.iterator().next();
			// return null if the id is null
			if (crudField.getValue() == null
					|| crudField.getValue().length() == 0) {
				return null;
			}

			if (crudField.getValue() == null
					|| crudField.getValue().length() == 0) {
				return null;
			}
			convertedId = ConvertUtils.convert(crudField.getValue(), crudField
					.getDef().getType());
		} else if (crudIdFields.size() > 1) {
			try {
				convertedId = this.getDef().getIdType().newInstance();
				for (CrudField crudField : crudIdFields) {
					// return null if any id field is null
					if (crudField.getValue() == null
							|| crudField.getValue().length() == 0) {
						return null;
					}
					BeanUtils.setProperty(convertedId, crudField.getDef()
							.getName(), crudField.getValue());
				}
			} catch (Exception e) {
				throw new RuntimeException(
						"Unable to create compound id of type "
								+ this.getDef().getIdType().getName(), e);
			}
		} else {
			throw new IllegalArgumentException("Unknown id field");
		}
		return convertedId;
	}

	/**
	 * @return the Collection of ids
	 */
	public Collection<CrudField> getIds() {
		Collection<CrudField> ids = new ArrayList<CrudField>();
		for (CrudField field : this.fieldsMap.values()) {
			if (field.getDef().isId()) {
				ids.add(field);
			}
		}
		return ids;
	}

	/**
	 * 
	 * @return a Collection of browsable fields sorted by position
	 */
	public Collection<CrudField> getBrowsableFields() {
		List<CrudField> fields = new ArrayList<CrudField>();
		for (CrudField field : this.fieldsMap.values()) {
			if (field.getDef().isBrowsable()) {
				fields.add(field);
			}
		}
		// Sort list
		Collections.sort(fields, FIELD_POSITION_COMPARATOR);
		return fields;
	}

	/**
	 * 
	 * @return a Collection of browse filter fields
	 */
	public Collection<CrudField> getBrowseFilterFields() {
		List<CrudField> fields = new ArrayList<CrudField>();
		for (CrudField field : this.fieldsMap.values()) {
			if (field.getDef().isBrowseFilter()) {
				fields.add(field);
			}
		}
		return fields;
	}

	public CrudBeanDef getDef() {
		return def;
	}

	public void setDef(CrudBeanDef def) {
		this.def = def;
	}

	public Collection<CrudField> getFields() {
		return fieldsMap.values();
	}

	public String getLabel() {
		return label;
	}

	public void setLabel(String label) {
		this.label = label;
	}

	public Map<String, CrudField> getFieldsMap() {
		return fieldsMap;
	}

	public void setFieldsMap(Map<String, CrudField> fieldsMap) {
		this.fieldsMap = fieldsMap;
	}

	public boolean isNewBean() {
		return newBean;
	}

	public void setNewBean(boolean newBean) {
		this.newBean = newBean;
	}

	public CrudField getRemoveMark() {
		for (CrudField field : this.fieldsMap.values()) {
			if (field.getDef().isRemoveMark()) {
				return field;
			}
		}
		return null;
	}

	@Override
	public boolean equals(Object obj) {
		if (!(obj instanceof CrudBean)) {
			return false;
		}

		CrudBean other = (CrudBean) obj;

		// Evaluate type equality
		if (!this.def.getType().equals(other.getDef().getType())) {
			return false;
		}

		// Evaluate values equality
		for (CrudField field : this.fieldsMap.values()) {
			Object value1 = field.getValue();
			Object value2 = other.getFieldsMap().get(field.getDef().getName())
					.getValue();
			if (!ObjectUtils.equals(value1, value2)) {
				return false;
			}
		}

		return true;
	}
}
